﻿// -----------------------------------------------------------------------
// <copyright file="EnumMember.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

using System.Data.Odbc;

namespace Centido.Core
{
    using System;


    /// <summary>
    ///   An enumeration member.
    /// </summary>
    /// <remarks>An enumeration member stores information about
    /// an allowed enumeration value such as its display name.</remarks>
    /// <example>
    ///   The following example shows how to use enumeration members.
    /// <code>
    ///     using System;
    ///     using System.Data.Common;
    ///     using Centido.Core;
    /// 
    ///     class Sample
    ///     {
    ///         static void Main()
    ///         {
    ///             var domainConfig = new DomainConfiguration()
    ///             {
    ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
    ///                 DbProviderName = "MSSQLSERVER"
    ///             };
    ///         
    ///             var domain = Domain.Build(domainConfig);
    ///         
    ///             domain.DeleteAll();
    ///             
    ///             //Adding a new table
    ///             var table = domain.AddTable("my_table");
    ///             table.GrantAll();
    ///             
    ///             //Creating an enumeration column
    ///             var column = table.AddColumn("enum_column", ColumnType.Enumeration)
    ///                 as EnumColumn;
    /// 
    ///             //Creating enumeration members
    ///             column.AddMember(1).DisplayName = "The first member";
    ///             column.AddMember(2).DisplayName = "The second member";
    /// 
    ///             //Making the second member to be default
    ///             column.DefaultValue = 2;
    /// 
    ///             //Inserting two rows
    ///             var firstRowId = table.InsertRow();
    ///             var secondRowId = table.InsertRow(column.Value(1));
    /// 
    ///             Console.WriteLine("The first row: value = {0}", column.Read(firstRowId).GetInteger());
    ///             Console.WriteLine("The second row: value = {0}", column.Read(secondRowId).GetInteger());
    /// 
    ///             //Trying to insert a wrong value
    ///             try
    ///             {
    ///                 table.InsertRow(column.Value(3));
    ///             }
    ///             catch(DbException)
    ///             {
    ///                 Console.WriteLine("Inserting invalid enumeration values is not allowed");
    ///             }
    /// 
    ///             //Changing the member value
    ///             Console.WriteLine("Changing the first member value");
    /// 
    ///             column.GetMember(1).Value = 3;
    /// 
    ///             Console.WriteLine("The first row: value = {0}", column.Read(firstRowId).GetInteger());
    ///             Console.WriteLine("The second row: value = {0}", column.Read(secondRowId).GetInteger());
    ///         }
    ///     }
    /// </code>
    /// </example>
    public sealed class EnumMember : IRemovable, IRefreshable
    {
        /// <summary>
        ///   The parent column.
        /// </summary>
        private readonly EnumColumn _column;

        /// <summary>
        ///   The value of the enumeration member.
        /// </summary>
        private int _value;

        /// <summary>
        ///   The enumeration member display name.
        /// </summary>
        private string _displayName;

        /// <summary>
        ///   Initializes a new instance of the <see cref = "Centido.Core.EnumMember" /> class
        ///   for the specified column and value.
        /// </summary>
        /// <param name = "column">The parent enumeration column.</param>
        /// <param name = "value">The value of the member.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="column"/> is null.</exception>
        internal EnumMember(EnumColumn column, int value)
        {
            if (column == null)
                throw new ArgumentNullException("column");

            _column = column;
            _value = value;
        }

        /// <summary>
        ///   Gets the parent domain.
        /// </summary>
        public Domain Domain
        {
            get { return Column.Domain; }
        }

        /// <summary>
        ///   Gets the parent enumeration column.
        /// </summary>
        public EnumColumn Column
        {
            get { return _column; }
        }

        /// <summary>
        ///   Gets or sets the value of the enumeration member.
        /// </summary>
        /// <remarks>
        /// <para>Setting enumeration member value to a new one results in changing that
        /// value in the rows that contain the old one. If the old value was
        /// a default one for the column then the default value is also changed
        /// to a new one.</para>
        /// <para>The current user has to have the permission to change the schema in order to set this property.</para>
        /// </remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to set the property.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when setting the property and there is already an enumeration member with the same value.</exception>
        /// <example>
        ///   The following example shows how to change the value of an enumeration member.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        ///             table.GrantAll();            
        /// 
        ///             //Creating an enumeration column
        ///             var column = table.AddColumn("enum_column", ColumnType.Enumeration)
        ///                 as EnumColumn;
        ///             
        ///             //Adding some enumeration members
        ///             column.AddMember(1);
        ///             column.AddMember(2);
        /// 
        ///             //Setting the default value
        ///             column.DefaultValue = 1;
        /// 
        ///             //Inserting some rows
        ///             var firstRowId = table.InsertRow();
        ///             var secondRowId = table.InsertRow(column.Value(2));
        /// 
        ///             //Showing the inserted values
        ///             Console.WriteLine("The first row: value = {0}", column.Read(firstRowId).GetInteger());
        ///             Console.WriteLine("The second row: value = {0}", column.Read(secondRowId).GetInteger());
        /// 
        ///             Console.WriteLine("Default value = {0}", column.DefaultValue);
        /// 
        ///             //Changing the first member value
        ///             Console.WriteLine("Changing the first member value");
        /// 
        ///             column.GetMember(1).Value = 3;
        /// 
        ///             //Showing the inserted values
        ///             Console.WriteLine("The first row: value = {0}", column.Read(firstRowId).GetInteger());
        ///             Console.WriteLine("The second row: value = {0}", column.Read(secondRowId).GetInteger());
        /// 
        ///             Console.WriteLine("Default value = {0}", column.DefaultValue);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public int Value
        {
            get { return _value; }
            set
            {
                if (_value == value) return;

                Domain.DbAccess.ExecuteNonQuery(
                    cmd =>
                    {
                        cmd.CommandText = string.Format("{{call {0}(?,?,?)}}",
                            Domain.GetObjectFullName(Db.Procedures.SetEnumMemberValue));

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_column_id",
                            OdbcType.Binary, Column.GetRawId()));
                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_old_value",
                            OdbcType.Int, _value));
                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_new_value",
                            OdbcType.Int, value));
                    });

                _value = value;

                // Refreshing the default value of the parent column
                Column.Refresh();
            }
        }

        /// <summary>
        ///   Gets or sets the display name of the enumeration member.
        /// </summary>
        /// <remarks>The current user has to have the permission to change the schema in order to set this property.</remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to set this property.</exception>
        /// <example>
        ///   The following example shows how to set the display name of enumeration members.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        ///             table.GrantAll();            
        /// 
        ///             //Creating an enumeration column
        ///             var column = table.AddColumn("enum_column", ColumnType.Enumeration)
        ///                 as EnumColumn;
        ///             
        ///             //Adding some enumeration members
        ///             column.AddMember(1).DisplayName = "The first member";
        ///             column.AddMember(2).DisplayName = "The second member";
        ///             column.AddMember(3).DisplayName = "The third member";
        /// 
        ///             //Enumerating the enumeration members
        ///             foreach(var m in column.Members)
        ///                 Console.WriteLine("Enumeration member: value = {0}, display name = {1}",
        ///                     m.Value, m.DisplayName);
        ///         }
        ///     }   
        /// </code>
        /// </example>
        public string DisplayName
        {
            get { return _displayName ?? (_displayName = 
                DbValue.GetString(ReadProperty(Db.Tables.EnumerationMemberList.Col.DisplayName))); }
            set
            {
                if (value != null)
                    value = value.Trim();

                if (!string.IsNullOrEmpty(_displayName) && _displayName == value) return;

                WriteProperty(Db.Tables.EnumerationMemberList.Col.DisplayName, value, OdbcType.NVarChar);

                _displayName = value;
            }
        }

        /// <summary>
        ///   Deletes the enumeration member.
        /// </summary>
        /// <remarks>
        /// <para>The current user has to have the permission in order to execute this function.</para>
        /// <para>Deleting of an enumeration member results in setting the column to null
        /// for those rows that contain the value of the deleted member.</para>
        /// </remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when there is no enumeration member with such value.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when there are rows that contain the value of the specified enumeration member and the column is set to be not nullable.</exception>
        /// <example>
        ///   The following example shows how to delete an enumeration member.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        ///             table.GrantAll();            
        /// 
        ///             //Creating an enumeration column
        ///             var column = table.AddColumn("enum_column", ColumnType.Enumeration)
        ///                 as EnumColumn;
        ///             
        ///             //Adding some enumeration members
        ///             var firstMember = column.AddMember(1);
        ///             column.AddMember(2);
        ///             column.AddMember(3);
        /// 
        ///             //Inserting two rows
        ///             var firstRowId = table.InsertRow(column.Value(1));
        ///             var secondRowId = table.InsertRow(column.Value(2));
        ///             var thirdRowId = table.InsertRow(column.Value(3));
        /// 
        ///             //Deleting the first member
        ///             firstMember.Delete();
        /// 
        ///             //Enumerating the enumeration members
        ///             foreach(var m in column.Members)
        ///                 Console.WriteLine("Enumeration member: value = {0}", m.Value);
        /// 
        ///             //Checking the row values
        ///             Console.WriteLine("First row: value = {0}", column.Read(firstRowId).GetInteger());
        ///             Console.WriteLine("Second row: value = {0}", column.Read(secondRowId).GetInteger());
        ///             Console.WriteLine("Third row: value = {0}", column.Read(thirdRowId).GetInteger());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void Delete()
        {
            Column.DeleteMember(Value);
        }

        /// <summary>
        ///   Checks the existence of the enumeration member.
        /// </summary>
        /// <returns>Returns true if the enumeration member still exists; otherwise, returns false.</returns>
        public bool Exists()
        {
            var wasFound = false;

            Domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format("select count(*) from {0} where {1}=? and {2}=?",
                        Domain.GetObjectFullName(Db.Tables.EnumerationMemberList.Name),
                        Db.Tables.EnumerationMemberList.Col.ColumnId,
                        Db.Tables.EnumerationMemberList.Col.Value);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("column_id", OdbcType.Binary, Column.GetRawId()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("member_value", OdbcType.Int, Value));
                },
                result => wasFound = DbValue.GetInteger(result) != 0);

            return wasFound;
        }

        /// <summary>
        ///   Reads an enumeration member property value.
        /// </summary>
        /// <param name = "propertyName">The property name.</param>
        /// <returns>Returns the property value.</returns>
        private object ReadProperty(string propertyName)
        {
            if (propertyName == null)
                throw new ArgumentNullException("propertyName");
            if (propertyName.Trim().Length == 0)
                throw new ArgumentException("Property name cannot be empty.");

            object resultValue = null;

            Column.Domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select {0} from {1} where {2}=? and {3}=?",
                        propertyName,
                        Domain.GetObjectFullName(Db.Tables.EnumerationMemberList.Name),
                        Db.Tables.EnumerationMemberList.Col.ColumnId,
                        Db.Tables.EnumerationMemberList.Col.Value);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("column_id", OdbcType.Binary, Column.GetRawId()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("value", OdbcType.Int, Value));
                },
                result => resultValue = result);

            return resultValue;
        }

        /// <summary>
        ///   Updates an enumeration member property.
        /// </summary>
        /// <param name = "propertyName">The property name.</param>
        /// <param name = "propertyValue">The property new value.</param>
        /// <param name = "propertyType">The property database type.</param>
        private void WriteProperty(string propertyName, object propertyValue, OdbcType propertyType)
        {
            if (propertyName == null)
                throw new ArgumentNullException("propertyName");
            if (propertyName.Trim().Length == 0)
                throw new ArgumentException("Property name cannot be empty.");

            var rowCount = Column.Table.Domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "update {0} set {1}=? where {2}=? and {3}=?",
                        Domain.GetObjectFullName(Db.Tables.EnumerationMemberList.Name),
                        propertyName,
                        Db.Tables.EnumerationMemberList.Col.ColumnId,
                        Db.Tables.EnumerationMemberList.Col.Value);

                    cmd.Parameters.Add(
                        Domain.DbAccess.CreateParameter("property", propertyType, DbValue.GetDbValue(propertyValue)));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("column_id", OdbcType.Binary, Column.GetRawId()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("value", OdbcType.Int, Value));
                });

            if (rowCount < 1)
                throw new DbOperationException();
        }


        /// <summary>
        ///   Refreshes the object fields.
        /// </summary>
        public void Refresh()
        {
            _displayName = null;   
        }
    }
}